package demo.api.ding;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiMediaUploadRequest;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiMediaUploadResponse;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.taobao.api.ApiException;
import com.taobao.api.FileItem;
import demo.vo.exp.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 钉钉API工具类
 *
 * <li>发起POST请求时，必须将字符集编码设置成UTF-8。
 * <li>每个机器人每分钟最多发送20条。消息发送太频繁会严重影响群成员的使用体验，大量发消息的场景 (譬如系统监控报警) 可以将这些信息进行整合，
 * 通过markdown消息以摘要的形式发送到群里。
 *
 * @author junying.han
 */
public class DingTalkApiUtils {

    public static final String ERR_MSG = "errmsg";

    public static final String ERR_CODE = "errcode";
    public static final RestTemplate client = new RestTemplate();
    public static final String OAPI_HOST = "https://oapi.dingtalk.com";
    public static final String CORP_ID = "ding51f195092fd77474";
    public static final String CORP_SECRET = "S5oe8UKsrPRygOfyJhzI74PqGZU4JxFAEX-Kmqk1F6Dg3P-X2h4HMNdL5iSxvElY";
    public static final String JISHUCENTER_ID = "5979016";
    private static final String APP_KEY = "dinghtzouhsyqes9zylt";
    private static final String APP_SECRET = "pdFD2zbdYudjwyK5SA3CunWTMtux4dhKzVSrGia3NrtOkUCAds8wobfaCzZjGWQ8";
    private static Logger logger = LoggerFactory.getLogger(DingTalkApiUtils.class);
    /**
     * token 缓存
     */
    private static final LoadingCache<Boolean, String> ACCESS_TOKEN_CACHE = CacheBuilder.newBuilder()
            .expireAfterWrite(60, TimeUnit.MINUTES).maximumSize(10).build(new CacheLoader<Boolean, String>() {
                @Override
                public String load(Boolean key) throws Exception {
                    JSONObject json = getAccessToken(key);
                    Integer errcode = json.getInteger(ERR_CODE);
                    String errmsg = json.getString(ERR_MSG);
                    if (errcode != null && errcode.intValue() == 0) {
                        return json.getString("access_token");
                    } else {
                        throw new ServiceException("getAccessToken failed: " + errmsg);
                    }
                }
            });
    /**
     * 手机号 -> 用户ID 缓存
     */
    public static final LoadingCache<String, String> MOBILE_USERID_CACHE = CacheBuilder.newBuilder()
            .expireAfterWrite(8, TimeUnit.HOURS).maximumSize(1024).build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    String userId = getUserIdByMobile(key);
                    return userId == null ? "" : userId;
                }
            });
    /**
     * 部门ID -> 用户列表信息 缓存
     */
    public static final LoadingCache<Long, List<JSONObject>> DEPARTMENTID_USERLIST = CacheBuilder.newBuilder()
            .expireAfterWrite(8, TimeUnit.HOURS).maximumSize(256).build(new CacheLoader<Long, List<JSONObject>>() {
                @Override
                public List<JSONObject> load(Long departmentId) throws Exception {
                    if (departmentId == null) {
                        return Collections.emptyList();
                    }
                    return getDepartmentUsers(departmentId);
                }
            });

    private DingTalkApiUtils() {
        super();
    }

    /**
     * 从缓存获取token
     *
     * @param isCorp 是否使用corpid
     * @return
     * @throws ExecutionException
     */
    public static String getTokenByCache(boolean isCorp) throws ExecutionException {
        return ACCESS_TOKEN_CACHE.get(isCorp);
    }

    /**
     * 请求token
     *
     * @return <pre>
     *         {"errcode":0,"access_token":"a85a01573e8d302a8bc0ac807e9ceee6","errmsg":"ok","expires_in":7200}
     *         </pre>
     */
    public static JSONObject getToken() {
        return getAccessToken(true);
    }

    /**
     * 获取钉钉Token。 【注意】正常情况下access_token有效期为7200秒(即2小时)，有效期内重复获取返回相同结果，并自动续期。
     *
     * @param isCorp 是否使用corpid
     * @return <pre>
     * {
     * "errcode": 0,
     * "access_token": "66b965702ec13a8c872aba9b65d4f630",
     * "errmsg": "ok",
     * "expires_in": 7200
     * }
     *         </pre>
     * @see <link> https://ding-doc.dingtalk.com/doc#/serverapi2/eev437
     */
    public static JSONObject getAccessToken(boolean isCorp) {
        StringBuilder sb = new StringBuilder();
        sb.append(OAPI_HOST).append("/gettoken?");
        if (isCorp) {
            sb.append("corpid=").append(CORP_ID).append("&corpsecret=").append(CORP_SECRET);
        } else {
            sb.append("appkey=").append(APP_KEY).append("&appsecret=").append(APP_SECRET);
        }
        String url = sb.toString();
        JSONObject json = client.getForObject(url, JSONObject.class);
        logger.warn("request {} return {}", url, json);
        return json;
    }

    /**
     * 根据员工手机号查询id
     *
     * @param mobile 手机号
     * @return <pre>
     * {
     * "errcode": 0,
     * "errmsg": "ok",
     * "userid": "7150"
     * }
     *         </pre>
     * @throws ExecutionException
     * @throws ServiceException
     */
    public static String getUserIdByMobile(String mobile) throws ExecutionException, ServiceException {
        String token = getTokenByCache(false);
        String url = OAPI_HOST + "/user/get_by_mobile?access_token=" + token + "&mobile=" + mobile;
        JSONObject responseJson = client.getForObject(url, JSONObject.class);
        logger.warn(" get_by_mobile {} return:{} ", mobile, responseJson);
        Integer errcode = responseJson.getInteger(ERR_CODE);
        String errmsg = responseJson.getString(ERR_MSG);
        if (errcode != null && errcode.intValue() == 0) {
            return responseJson.getString("userid");
        } else {
            throw new ServiceException("getUserIdByMobile failed: " + errmsg);
        }
    }

    /**
     * 获取部门所有员工详细信息
     *
     * @param departmentId 部门id
     * @return <pre>
     * {
     * "errcode": 0,
     * "userlist": [
     * {
     * "unionid": "Pfu1hZsLakkftjN0f92xDgiEiE",
     * "userid": "2608",
     * "isBoss": false,
     * "hiredDate": 1426176000000,
     * "department": [
     * 5993166
     * ],
     * "order": 180169421593145768,
     * "isLeader": true,
     * "active": true,
     * "isAdmin": false,
     * "avatar": "",
     * "isHide": false,
     * "jobnumber": "2608",
     * "name": "胡XX",
     * "position": ""
     * },
     * {
     * "unionid": "MfabjmwTSnUftjN0f92xDgiEiE",
     * "userid": "04056018211262955",
     * "isBoss": false,
     * "hiredDate": 1520352000000,
     * "department": [
     * 5993166
     * ],
     * "order": 177917621779460510,
     * "isLeader": false,
     * "active": true,
     * "isAdmin": false,
     * "avatar": "",
     * "isHide": false,
     * "jobnumber": "4532",
     * "name": "鲁X",
     * "position": ""
     * }
     * ],
     * "hasMore": false,
     * "errmsg": "ok"
     * }
     *         </pre>
     * @throws ExecutionException
     * @throws ServiceException
     */
    public static List<JSONObject> getDepartmentUsers(long departmentId) throws ExecutionException, ServiceException {
        String token = getTokenByCache(true);
        JSONObject responseJson;
        List<JSONObject> users = new ArrayList<>();
        int offset = 0;
        do {
            String url = OAPI_HOST + "/user/listbypage?access_token=" + token + "&department_id=" + departmentId
                    + "&offset=" + offset + "&size=" + 50;
            responseJson = client.getForObject(url, JSONObject.class);
            Integer errcode = responseJson.getInteger(ERR_CODE);
            String errmsg = responseJson.getString(ERR_MSG);
            if (errcode != null && errcode.intValue() != 0) {
                logger.warn("getDepartmentUsers by {} return :{} ", departmentId, responseJson);
                throw new ServiceException(String.format("getDepartmentUsers by %s failed: %s", departmentId, errmsg));
            }
            JSONArray userList = responseJson.getJSONArray("userlist");
            for (int i = 0; i < userList.size(); i++) {
                users.add(userList.getJSONObject(i));
            }
            offset += 50;
        } while (Boolean.TRUE.equals(responseJson.getBoolean("hasMore")));
        return users;
    }

    /**
     * 发送钉钉机器人消息
     *
     * @param msg
     * @param robotUrl
     * @param mobiles
     * @return <pre>
     * {"errcode":0,"errmsg":"ok"}
     *         </pre>
     */
    public static JSONObject chatSendAsText(String msg, String robotUrl, Collection<String> mobiles, boolean isAtAll) {
        Map<String, String> textMap = new HashMap<>();
        textMap.put("content", msg);

        Map<String, Object> atMap = new HashMap<>();
        atMap.put("atMobiles", mobiles);
        atMap.put("isAtAll", isAtAll);

        JSONObject body = new JSONObject();
        body.put("msgtype", "text");
        body.put("text", textMap);
        body.put("at", atMap);

        return client.postForObject(robotUrl, body, JSONObject.class);
    }

    /**
     * @param title    首屏会话透出的展示内容。
     * @param text     markdown格式的消息。
     * @param robotUrl 钉钉机器人
     * @param mobiles  被@人的手机号。在text内容里要有@人的手机号。
     * @param atAll    是否@所有人
     * @throws ApiException
     * @throws ServiceException
     */
    public static OapiRobotSendResponse sendMarkdown(String title, String text, String robotUrl, Collection<String> mobiles, boolean atAll)
            throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient(robotUrl);

        OapiRobotSendRequest request = new OapiRobotSendRequest();
        request.setMsgtype("markdown");

        OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
        at.setAtMobiles(new ArrayList<>(mobiles));
        at.setIsAtAll(atAll);
        request.setAt(at);

        OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown();
        markdown.setTitle(title);
        markdown.setText(text);
        request.setMarkdown(markdown);
        return client.execute(request);
    }

    /**
     * @param title      首屏会话透出的展示内容。
     * @param text       markdown格式的消息。
     * @param robotUrl   钉钉机器人
     * @param mobiles    被@人的手机号。在text内容里要有@人的手机号。
     * @param atAll      是否@所有人
     * @param buttonList 按钮
     * @throws ApiException
     * @throws ServiceException
     */
    public static OapiRobotSendResponse sendActionCard(String title, String text, String robotUrl, Collection<String> mobiles,
                                                       boolean atAll, List<OapiRobotSendRequest.Btns> buttonList)
            throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient(robotUrl);
        OapiRobotSendRequest request = new OapiRobotSendRequest();
        request.setMsgtype("actionCard");

        OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
        at.setAtMobiles(new ArrayList<>(mobiles));
        at.setIsAtAll(atAll);
        request.setAt(at);

        OapiRobotSendRequest.Actioncard actionCard = new OapiRobotSendRequest.Actioncard();
        actionCard.setTitle(title);
        actionCard.setText(text);
        // 0-按钮竖直排列，1-按钮横向排列
        String btnOrientation = buttonList != null && buttonList.size() == 2 ? "1" : "0";
        actionCard.setBtnOrientation(btnOrientation);
        actionCard.setHideAvatar("0");
        actionCard.setBtns(buttonList);

        request.setActionCard(actionCard);
        return client.execute(request);
    }

    /**
     * @param content  消息内容
     * @param robotUrl 钉钉机器人
     * @param mobiles  被@人的手机号
     * @param atAll    是否@所有人
     * @return
     * @throws ApiException
     */
    public static OapiRobotSendResponse sendText(String content, String robotUrl, Collection<String> mobiles, boolean atAll)
            throws ApiException {
        if (content == null || content.isEmpty()) {
            return null;
        }
        DingTalkClient client = new DefaultDingTalkClient(robotUrl);
        OapiRobotSendRequest request = new OapiRobotSendRequest();
        request.setMsgtype("text");

        OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
        text.setContent(content);
        request.setText(text);

        OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
        at.setAtMobiles(new ArrayList<>(mobiles));
        // isAtAll类型如果不为Boolean，请升级至最新SDK
        at.setIsAtAll(atAll);
        request.setAt(at);
        return client.execute(request);
    }

    /**
     * 获取部门信息
     *
     * @param departmentId
     * @return <pre>
     * {
     * "errcode": 0,
     * "errmsg": "ok",
     * "department": []
     * }
     *         </pre>
     * @throws ExecutionException
     */
    public static JSONObject departmentList(String departmentId) throws ExecutionException {
        String token = getTokenByCache(true);
        String url = OAPI_HOST + "/department/list?access_token=" + token + "&id=" + departmentId;

        JSONObject reponseJson = client.getForObject(url, JSONObject.class);
        logger.info("departmentList by :{} return :{} ", departmentId, reponseJson);
        return reponseJson;
    }

    /**
     * 部门下的所有用户
     *
     * @param departmentId
     * @return <pre>
     * {
     * "errcode": 0,
     * "userlist": [
     * {
     * "unionid": "yaQMfKrBvnIftjN0f92xDgiEiE",
     * "openId": "yaQMfKrBvnIftjN0f92xDgiEiE",
     * "remark": "",
     * "userid": "2072",
     * "isBoss": false,
     * "hiredDate": 1278259200000,
     * "tel": "",
     * "department": [
     * 5979016,
     * 63946328,
     * 87786846,
     * 87829864,
     * 130916817,
     * 61667564,
     * 87753877
     * ],
     * "workPlace": "上海",
     * "email": "gavin@ximalaya.com",
     * "order": 180169421593145766,
     * "dingId": "$:LWCP_v1:$sqvR1AFoBddG7iLzQP/bjw==",
     * "isLeader": true,
     * "mobile": "13585745115",
     * "active": true,
     * "isAdmin": false,
     * "avatar": "https://static-legacy.dingtalk.com/media/lADPBbCc1cetsmjNA0vNBGU_1125_843.jpg",
     * "isHide": false,
     * "jobnumber": "2072",
     * "name": "XXXXX",
     * "extattr": {
     * "英文名字": "gavin"
     * },
     * "stateCode": "86",
     * "position": ""
     * },
     * {
     * ...
     * }
     * ],
     * "errmsg": "ok"
     * }
     *         </pre>
     * @throws ExecutionException
     */
    public static JSONObject userList(String departmentId) throws ExecutionException {
        String token = getTokenByCache(true);
        String url = OAPI_HOST + "/user/list?access_token=" + token + "&department_id=" + departmentId;

        JSONObject reponseJson = client.getForObject(url, JSONObject.class);
        logger.info("departmentList by :{} return :{} ", departmentId, reponseJson);
        return reponseJson;
    }

    public static String imageUpload(String filePath) throws ExecutionException, ApiException {
        OapiMediaUploadResponse response = mediaUpload(filePath);
        if (!response.isSuccess()) {
            logger.error("mediaUpload failed: {}", JSON.toJSONString(response));
            return "";
        }
        String token = getTokenByCache(true);
        return "https://oapi.dingtalk.com/media/downloadFile?access_token=" + token + "&media_id=" + response.getMediaId();
    }

    /**
     * 基于文件绝对路径的构造器，适用于上传本地文件。
     * <a href="https://open.dingtalk.com/document/orgapp/upload-media-files">上传媒体文件<a/>
     *
     * @param filePath
     * @return
     */
    public static OapiMediaUploadResponse mediaUpload(String filePath) throws ExecutionException, ApiException {
        String token = getTokenByCache(true);
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/media/upload");
        OapiMediaUploadRequest req = new OapiMediaUploadRequest();
        req.setType("image");
        // 要上传的媒体文件
        FileItem item = new FileItem(filePath);
        req.setMedia(item);
        return client.execute(req, token);
    }
}
